這是我們一開始的程式碼
var myName = '真心鎮大冒險';
var family = {
myName: '小明家'
};
function fn (para1, para2) {
console.log(this, para1, para2);
}
那麼我們要介紹的是 call, apply, bind 這三種方法,來改變 this
的指向
這三個的方法觀念都很相近,只是呼叫方法上不太一樣,我們來看看上一個章節我們提到的 簡易呼叫(Simple Call),利用這個 fn 的函式印出來的結果是如何?
var myName = '真心鎮大冒險';
var family = {
myName: '小明家'
};
function fn (para1, para2) {
console.log(this, para1, para2);
}
fn(1, 2); // 簡易呼叫(Simple Call)
我們可以看到運用 簡易呼叫(Simple Call) 的方式, this
的指向當然是指向 Window 物件。並且傳入的參數是 1 跟 2,這個應該沒甚麼問題。
fn.call(family, 1, 2);
// fn.call(要讓 this 指向的對象, 參數1, 參數2);
這邊再要執行的function後面接上 .call()
,並且第一個參數是給予要讓前面的 function 執行環境中的 this 要指向哪個對象,通常是給予物件型別的資料,再來後面就是依據傳入該 function要傳入的參數。
所以結果會是
那麼在使用 call 的時候要特別注意,他是立刻執行這個函式,跟我們利用簡易呼叫(Simple Call)的方式有點像,主要的差別就是可以透過 call 改變 this 的指向。
apply 的跟 call 很像,只是傳入參數給 function 的形式不太一樣
fn.apply(family, [3, 4]);
// fn.apply(要讓 this 指向的對象, [參數1, 參數2]);
使用 apply 方法的時候,要傳入的參數必須包在一個陣列裡面,當作第二個參數傳入。
同樣也是立即執行,第一個參數也是用來改變執行環境的 this 指向。
bind的方法跟call, apply的主要差異在於,他不會立刻執行前面的 function,因此要使用bind的方法的時候呢,必須先做一些處理。
var fn2 = fn.bind(family, '小明', '杰倫');
fn2();
先把 fn.bind(family, '小明', '杰倫')
的內容用變數盛裝以後,再利用執行函式的方式執行這個變數。
那麼在執行的過程中,就會自動替換 function 的 this
指向。
那麼在 fn2() 的小括號中帶入參數的話會怎麼樣嗎?
答案是不會,因為我們一開始在定義 fn2 的時候就已經定義好要帶入的參數了。
但是如果一開始沒有定義好帶入的參數的話呢?
var fn2 = fn.bind(family, '小明');
fn2(1, 2);
這樣的話,少了一個參數,但我又在fn2的小括號中帶入1跟2的參數,那麼會用哪個參數呢?
答案是會從 fn2 傳入的第一個參數當作是 fn 的第二的參數,依序補足不夠的參數。
另外一個要注意的是,雖然 fn2()
這樣的形式是 簡易呼叫(Simple Call),但是 fn 的 this 在一開始的時候就被指定了,所以這邊就算是用簡易呼叫(Simple Call)的方式, fn 的this 還是會被 bind 綁定給指定的物件。
到目前為止,我們知道可以透過 call, apply, bind 來改變 this 的指向。
那麼如果是下面這樣的狀況,會發生甚麼事情呢?
fn.call(1, '小明', '杰倫');
如果是傳入其他非物件型別的原始型別資料,會自動被轉換成建構式方式的物件型別。
fn.call('文字', '小明', '杰倫');
我們再將 fn 多加上一個 typeof(this)
var myName = '真心鎮大冒險';
var family = {
myName: '小明家'
};
function fn (para1, para2) {
console.log(this, typeof(this), para1, para2);
}
存檔之後的結果就是
那我們再來傳一次不同的值看看
fn.call(undefined, '小明', '杰倫');
沒想到傳入了 undefined,居然會直接被指向到 window?!?!?1
為甚麼呢?我們來看一下 MDN的文件 怎麼說:
也就是說,如果再 非嚴格模式 的狀態下,就會將傳入的非物件型別資料封裝,也就是利用建構式封裝。
並且如果傳入的是 null undefined的話,就會被置換成是全域變數。
喔喔~這樣應該就能夠了解為什麼剛剛傳入的是 undefined。
但後來 this 會指向為 window了。
但到底甚麼是嚴格模式阿???
因為 Javascript 是相對寬鬆的程式,因此有很多不嚴謹的程式碼也能夠運行,但在維護上就會相對麻煩,並且不容易除錯。
在 ES5 之後就提供了開發者這種語法受限的模式,那麼這個模式下呢就有以下這些特點
嚴格模式呢,只要在程式碼中加入 'use strict' 的字串就可以。
主要是因為只撰寫 'use strict' 的字串 是屬於一種表達式,那麼這個程式碼不會影響不支援嚴格模式的瀏覽器。
並且可以加在特定的函式內進行嚴格模式,也就是說只有在特定的大括號內中執行的程式碼才會套用嚴格模式。當然如果在全域的環境使用的話,你所有的程式碼都會進入嚴格模式喔!
(function () {
'use strict';
// 這個執行環境進入了嚴格模式
})();
那麼在嚴格模式中,透過拋出錯誤的方式來提醒開發者一些安靜的錯誤。也可以避免掉一些開發的不好習慣,同時也有利於偵錯。
其中一個例子就是,如果在嚴格模式下,沒有使用 var let const 等宣告變數的方式進行宣告的話,他會直接跳錯。
在嚴格模式下呢,他不允許你直接對變數賦予值,他會要你先宣告這個變數之後,再對變數賦予值。
那麼其他更詳細,有關嚴格模式的內容,可以參考 MDN的文件
不過要特別注意的是,在嚴格模式的狀態下,每個瀏覽器的結果可能會有不同,所以有時候文件跟實際的狀況有出入也是屬於正常的。要特別有心理準備~XDD
那麼接著我們再來看一下嚴格模式的其他例子吧~
function callStrict (para1, para2) {
'use strict';
console.log(this, typeof(this), para1, para2);
}
callStrict.call(1, '小明', '杰倫');
這樣會發生甚麼事情呢?
可以看到在嚴格模式下,this 的指向就是我們透過 call 所傳入的 數字1。
並且沒有被封裝成建構式的形式!
那再來看看傳入 undefined 給大家看看
callStrict.call(undefined, '小明', '杰倫');
原本我們再非嚴格模式(sloppy mode)下,傳入 undefined 的話就會將 this 指向全域的window 物件。
那麼再嚴格模式下,傳入 undefined 的時候就還是會維持 undefined。
那麼我如果簡易呼叫 callStrict 的話會怎麼樣呢?
callStrict('小明', '杰倫');
那麼為什麼會這樣呢?
其實你使用簡易呼叫的時候,就等於是直接套用call這個方式進行呼叫。只是我們第一個參數沒有傳入,所以是 undefined。
那麼如果再嚴格模式下的話,當然就會是 undefined;如果是非嚴格模式(sloppy mode)下,雖然會自動將 this 自動指向到 window 物件,可是它的本質其實是 undefined。
這也是為什麼前面說的,在使用簡易呼叫的時候,盡量不要去調用他的 this, 因為它的本質其實是 undefined。
好~那麼這篇文章就介紹到這邊,沒問題就往下繼續看下去吧~!汪汪